﻿// =============================================================================
// TNTTools ObjectInfo.cs
// Copyright (c) 2013 TNTTechnologies Inc.
// =============================================================================

#region

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

using TNTTools.Reflection.Interfaces;
using TNTTools.Serialization.Interfaces;

#endregion

namespace TNTTools.Reflection{
    public class ObjectInfo{
        public Attributes classAttributes;
        public List<ReflectiveProperty> propertyInfo = new List<ReflectiveProperty>();
        private bool usedNonSerializerCtor = false;

        /// <summary>
        ///     Creates a reflection object
        ///     Use GetCachedObjectInfo to instantiate a singleton
        /// </summary>
        /// <param name="sourceType"></param>
        /// <param name="source"></param>
        /// <param name="serializer"></param>
        /// <param name="basicTypes"></param>
        public ObjectInfo(Type sourceType, object source, ISimpleSerializer serializer, IBasicTypes basicTypes){
            if (this.usedNonSerializerCtor){
                throw new InvalidOperationException(
                    "You can only use one ctor for creation of object info as they instantiate differently. Clear the cache and rebuild it or create a separate cache entity");
            }
            PropertyInfo[] properties = sourceType.GetProperties();

            List<ReflectiveProperty> nonBasicTypes = new List<ReflectiveProperty>();
            int i = 0;
            for (; i < properties.Length; ++i){
                ReflectiveProperty info = this.CreatePropertyType(
                    properties[i],
                    ((properties[i].PropertyType.IsSubclassOf(typeof(IList))
                      || IsIEnumerableOfT(properties[i].PropertyType)) && properties[i].PropertyType != typeof(String)),
                    new Attributes(properties[i]),
                    new GetterMethod(properties[i], source),
                    new SetterMethod(properties[i]));

                if (serializer.GetAttributeTypes().Contains(properties[i].PropertyType) == false){
                    nonBasicTypes.Add(info);
                } else{
                    this.propertyInfo.Add(info);
                }
            }
            this.propertyInfo[this.propertyInfo.Count - 1].IsLastBasicTypeAttribute = true;

            // Important: put non basic types last otherwise the xml format will be incorrect
            this.propertyInfo.AddRange(nonBasicTypes.ToArray());
            this.classAttributes = new Attributes(sourceType.GetCustomAttributes(true));
        }

        /// <summary>
        ///     Creates a reflection object with
        ///     Note, will not work with serializer as properties are not sorted.
        ///     Note, do not call GetType() post caching, that is slow, that is why we must pass the type in.
        /// </summary>
        /// <param name="source"></param>
        public ObjectInfo(object source, Type sourceType){
            this.usedNonSerializerCtor = true;
            PropertyInfo[] properties = sourceType.GetProperties();
            int i = 0;
            for (; i < properties.Length; ++i){
                ReflectiveProperty info = this.CreatePropertyType(
                    properties[i],
                    ((properties[i].PropertyType.IsSubclassOf(typeof(IList))
                      || IsIEnumerableOfT(properties[i].PropertyType)) && properties[i].PropertyType != typeof(String)),
                    new Attributes(properties[i].GetCustomAttributes(true)),
                    new GetterMethod(properties[i], source),
                    new SetterMethod(properties[i]));
                this.propertyInfo.Add(info);
            }
            this.propertyInfo[this.propertyInfo.Count - 1].IsLastBasicTypeAttribute = true;
            this.classAttributes = new Attributes(sourceType.GetCustomAttributes(true));
        }

        public virtual ReflectiveProperty CreatePropertyType(
            PropertyInfo propertyInfo,
            bool isList,
            Attributes classMarkUpAttributesXml,
            GetterMethod getter,
            SetterMethod setter){
            return new ReflectiveProperty(propertyInfo, isList, classMarkUpAttributesXml, getter, setter);
        }

        public static bool IsIEnumerableOfT(Type type){
            return
                type.GetInterfaces().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>));
        }
    }
}